/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License Version 2.0 with LLVM Exceptions
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *   https://llvm.org/LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once

#include <unifex/config.hpp>
#include <unifex/execution_policy.hpp>
#include <unifex/just.hpp>
#include <unifex/let_value.hpp>
#include <unifex/receiver_concepts.hpp>
#include <unifex/sender_concepts.hpp>
#include <unifex/type_list.hpp>

#include <unifex/detail/prologue.hpp>

namespace unifex {

namespace _let_v_w {

template <typename StateFactory, typename InnerOp, typename Receiver>
struct _operation {
  struct type;
};

template <typename StateFactory, typename InnerOp, typename Receiver>
using operation = typename _operation<StateFactory, InnerOp, Receiver>::type;

template <typename StateFactory, typename SuccessorFactory>
struct _sender {
  class type;
};

template <typename StateFactory, typename SuccessorFactory>
using let_with_sender = typename _sender<StateFactory, SuccessorFactory>::type;

template <typename StateFactory, typename SuccessorFactory>
class _sender<StateFactory, SuccessorFactory>::type {
public:
  using InnerOp =
      std::invoke_result_t<SuccessorFactory, std::invoke_result_t<StateFactory>&>;

  template <
      template <typename...>
      class Variant,
      template <typename...>
      class Tuple>
  using value_types = sender_value_types_t<InnerOp, Variant, Tuple>;

  template <template <typename...> class Variant>
  using error_types = sender_error_types_t<InnerOp, Variant>;

  static constexpr bool sends_done = sender_traits<InnerOp>::sends_done;

  static constexpr blocking_kind blocking = sender_traits<InnerOp>::blocking;

  static constexpr bool is_always_scheduler_affine =
      sender_traits<InnerOp>::is_always_scheduler_affine;

  template <typename StateFactory2, typename SuccessorFactory2>
  explicit type(StateFactory2&& stateFactory, SuccessorFactory2&& func)
    : stateFactory_((StateFactory2 &&) stateFactory)
    , func_((SuccessorFactory2 &&) func) {}

  template(typename Self, typename Receiver)                                 //
      (requires same_as<remove_cvref_t<Self>, type> AND receiver<Receiver>)  //
      friend auto tag_invoke(
          tag_t<unifex::connect>,
          Self&& self,
          Receiver&&
              r) noexcept(std::is_nothrow_invocable_v<member_t<Self, StateFactory>>&&
                              std::is_nothrow_invocable_v<
                                  member_t<Self, SuccessorFactory>,
                                  std::invoke_result_t<
                                      member_t<Self, StateFactory>>&>&&
                                  is_nothrow_connectable_v<
                                      std::invoke_result_t<
                                          member_t<Self, SuccessorFactory>,
                                          std::invoke_result_t<
                                              member_t<Self, StateFactory>>&>,
                                      remove_cvref_t<Receiver>>) {
    return operation<StateFactory, SuccessorFactory, Receiver>(
        static_cast<Self&&>(self).stateFactory_,
        static_cast<Self&&>(self).func_,
        static_cast<Receiver&&>(r));
  }

private:
  UNIFEX_NO_UNIQUE_ADDRESS StateFactory stateFactory_;
  UNIFEX_NO_UNIQUE_ADDRESS SuccessorFactory func_;
};

template <typename StateFactory, typename SuccessorFactory, typename Receiver>
struct _operation<StateFactory, SuccessorFactory, Receiver>::type {
  template <
      typename StateFactory2,
      typename SuccessorFactory2,
      typename Receiver2>
  type(StateFactory2&& stateFactory, SuccessorFactory2&& func, Receiver2&& r)
    : stateFactory_(static_cast<StateFactory2&&>(stateFactory))
    , func_(static_cast<SuccessorFactory2&&>(func))
    , state_(static_cast<StateFactory&&>(stateFactory_)())
    , innerOp_(unifex::connect(
          static_cast<SuccessorFactory&&>(func_)(state_),
          static_cast<Receiver2&&>(r))) {}

  void start() & noexcept { unifex::start(innerOp_); }

  StateFactory stateFactory_;
  SuccessorFactory func_;
  std::invoke_result_t<StateFactory> state_;
  connect_result_t<
      std::invoke_result_t<SuccessorFactory, std::invoke_result_t<StateFactory>&>,
      remove_cvref_t<Receiver>>
      innerOp_;
};

namespace _cpo {
struct _fn {
  template(typename StateFactory, typename SuccessorFactory)  //
      (requires std::is_invocable_v<std::decay_t<StateFactory>> AND std::is_invocable_v<
          std::decay_t<SuccessorFactory>,
          std::invoke_result_t<std::decay_t<StateFactory>>&> AND
           sender<std::invoke_result_t<
               std::decay_t<SuccessorFactory>,
               std::invoke_result_t<std::decay_t<StateFactory>>&>>)  //
      auto
      operator()(
          StateFactory&& stateFactory,
          SuccessorFactory&& successor_factory) const
      noexcept(std::is_nothrow_constructible_v<
               std::decay_t<SuccessorFactory>,
               SuccessorFactory>&&
                   std::is_nothrow_constructible_v<
                       std::decay_t<StateFactory>,
                       StateFactory>)
          -> _let_v_w::let_with_sender<
              std::decay_t<StateFactory>,
              std::decay_t<SuccessorFactory>> {
    return _let_v_w::let_with_sender<
        std::decay_t<StateFactory>,
        std::decay_t<SuccessorFactory>>{
        (StateFactory &&) stateFactory,
        (SuccessorFactory &&) successor_factory};
  }
};
}  // namespace _cpo
}  // namespace _let_v_w

inline constexpr _let_v_w::_cpo::_fn let_value_with{};

}  // namespace unifex

#include <unifex/detail/epilogue.hpp>
